"""
@Time ： 2025/9/10 10:05
@Auth ： zh
@File ：1.41措施项目费获取.py
"""
"""
@Time ： 2025/9/10 10:05
@Auth ： zh
@File ：1.41措施项目费获取.py
"""
# 用于执行匹配各类措施项目清单合价计算
import requests
import json
import sys
from typing import List, Dict, Union

# -------------------------- 1. 配置基础参数 --------------------------
# 第一步接口：获取structureId列表
FIRST_API_URL = "https://test.icii.agtsci.cn/api/cis/quota/projectStructure/tree"
# 第二步接口：获取清单数据
SECOND_API_URL = "https://test.icii.agtsci.cn/api/cis/quota/collect/bill/billTree"


# -------------------------- 2. 核心工具函数：生成前缀匹配的编码范围 --------------------------
def generate_prefix_code_range(start: str, end: str) -> List[str]:
    """
    根据起始和结束编码生成“前缀范围”（支持匹配带后缀的编码）
    例：start=粤011701008，end=粤011701020 → 生成["粤011701008", "粤011701009", ..., "粤011701020"]
    每个生成的编码可作为前缀，匹配如“粤011701008002”这类带后缀的编码
    """
    try:
        # 1. 分离编码的“前缀固定部分”和“数字递增部分”
        # 例：“粤011701008” → 固定前缀=“粤”，数字部分=“011701008”
        fixed_prefix = ""
        num_str = ""
        for c in start:
            if c.isdigit():
                num_str += c
            else:
                fixed_prefix += c

        # 2. 转换数字部分为整数，生成范围
        start_num = int(num_str)
        # 提取结束编码的数字部分（确保与起始编码的数字长度一致）
        end_num_str = ''.join([c for c in end if c.isdigit()])
        end_num = int(end_num_str)

        # 3. 生成带固定前缀的数字范围（保持数字部分的原始长度，避免补零错误）
        code_prefixes = []
        for num in range(start_num, end_num + 1):
            # 按起始编码的数字长度补零（例：num=8 → 补为“008”以匹配“粤011701008”格式）
            formatted_num = str(num).zfill(len(num_str))
            code_prefix = f"{fixed_prefix}{formatted_num}"
            code_prefixes.append(code_prefix)

        return code_prefixes

    except (ValueError, TypeError, IndexError) as e:
        print(f"⚠️  编码范围生成失败（{start}~{end}），错误：{str(e)}")
        return [start, end]  # 异常时返回原始编码


# -------------------------- 3. 匹配规则配置（前缀匹配版） --------------------------
# 各类别匹配规则配置：(类别名称, 编码前缀列表, 名称包含关键词)
# 注：编码前缀列表中的每个元素会作为“前缀”匹配billCode（如“粤011701008”匹配“粤011701008002”）
MATCH_RULES = [
    # 3.1 脚手架：支持前缀匹配（如粤011701008~粤011701020匹配带后缀的编码）
    ("脚手架",
     [*generate_prefix_code_range("粤011701008", "粤011701020"),  # 生成前缀：粤011701008~粤011701020
      "031301017",  # 直接匹配（完整编码或前缀）
      *generate_prefix_code_range("041101001", "041101005"),  # 生成前缀：041101001~041101005
      *generate_prefix_code_range("050401001", "050401007"),  # 生成前缀：050401001~050401007
      "粤050401008"],  # 直接匹配（完整编码或前缀）
     []),

    # 3.2 垂直运输（保持原规则，支持前缀匹配）
    ("垂直运输",
     ["011703001", "粤011703002"],
     []),

    # 3.3 大型机械进出场及安拆（保持原规则）
    ("大型机械进出场及安拆",
     ["011705001", "031301005", "041106001"],
     []),

    # 3.4 施工排水（需同时匹配编码前缀和名称关键词）
    ("施工排水",
     ["011706001", "011706002", "041107001", "041107002"],
     ["排水"]),

    # 3.5 施工降水（需同时匹配编码前缀和名称关键词）
    ("施工降水",
     ["011706001", "011706002", "041107001", "041107002"],
     ["降水"]),

    # 其余规则保持不变（均支持前缀匹配）
    ("临时设施", ["011707006", "050405007"], []),
    ("文明施工", ["011707001", "031302001", "041109001", "050405001"], []),
    ("环境保护", [], []),
    ("安全生产", [], []),
    ("冬雨季施工增加", ["011707005", "031302005", "041109004", "050405005"], []),
    ("夜间施工增加", ["011707002", "011707003", "031302002", "031302003", "041109002", "050405002", "050405003"], []),
    ("特殊地区施工增加", ["031301009"], []),
    ("二次搬运", ["011707004", "031302004", "041109003", "050405004"], []),
    ("已完工程及设备保护", ["011707007", "031302006", "041109007", "050405008"], []),
    ("既有建(构)筑物、设施保护", ["041109006"], []),
    ("临时专用防护棚", ["031301008"], []),
    ("施工操作平台", ["031301003"], []),
    ("临时支撑架", [], []),
    ("隧道(洞)内临时设施", ["031301016", "041105001", "041105002", "041105003", "041105004"], []),
    ("吊装加固", ["031301001", "031301002"], []),
    ("胎(模)具", ["031301007"], []),
    ("顶升、提升装置", ["031301004"], []),
    ("安装与生产同时施工防护", ["031301010"], []),
    ("有害环境施工防护", ["031301011"], []),
    ("水上桩基础支架平台", ["041102039"], []),
    ("桥涵支架", ["041102040"], []),
    ("围堰", ["041103001"], []),
    ("筑岛", ["041103002"], []),
    ("便道", ["041104001"], []),
    ("便桥", ["041104002"], []),
    ("洞内外轨道铺设", ["041105005"], []),
    ("立交箱涵顶进加固防护", [], []),
    ("地下管线交叉处理", ["041108001"], []),
    ("施工监测、监控", ["041108002"], []),
    ("植物养护", [], []),
    ("反季节栽植影响措施", ["050405006"], []),
    ("树木支撑架", ["050403001"], []),
    ("树木裘干", ["050403002"], []),
    ("搭设遮阴(防寒)棚", ["050403003"], []),
    ("树木输液", [], []),
    ("树干涂白", [], []),
    ("假植", [], []),
    ("设施顶面绿化垂直运输", ["粤050405012"], []),
    ("其他措施", ["031301018"], [])
]


# -------------------------- 4. 其他工具函数 --------------------------
def get_quota_id_dynamically() -> str | None:
    """动态获取quotaId，支持命令行参数或手动输入，含基础校验"""
    if len(sys.argv) >= 2:
        quota_id = sys.argv[1].strip()
        if quota_id and 10 <= len(quota_id) <= 30 and quota_id.isdigit():
            print(f"从命令行获取到quotaId：{quota_id}")
            return quota_id
        else:
            print("命令行传入的quotaId格式无效，切换为手动输入")

    while True:
        quota_id = input("请输入quotaId（通常为10-30位数字）：").strip()
        if not quota_id:
            print("错误：quotaId不能为空，请重新输入！")
            continue
        if not quota_id.isdigit():
            print("错误：quotaId应为纯数字，请重新输入！")
            continue
        if len(quota_id) < 10 or len(quota_id) > 30:
            print("错误：quotaId长度应在10-30位之间，请重新输入！")
            continue
        confirm = input(f"您输入的quotaId为：{quota_id}，是否确认？（y/n）：").strip().lower()
        if confirm == "y":
            return quota_id
        print("请重新输入quotaId...")


def send_post_request(url: str, params: Dict) -> Union[Dict, None]:
    """发送POST请求，处理超时、状态码等异常，返回JSON响应"""
    headers = {
        "Content-Type": "application/json",
        "Authorization": "bearer d2b663b6-5195-4efe-b7c9-d5d14e937b6c"
    }
    try:
        response = requests.post(
            url=url,
            data=json.dumps(params),
            headers=headers,
            timeout=15
        )
        response.raise_for_status()
        result = response.json()

        if result.get("code") == 200:
            print(f"✅ 请求成功：{url}")
            return result
        else:
            print(f"❌ 接口返回失败：{result.get('message', '未知错误')}")
            return None

    except requests.exceptions.RequestException as e:
        print(f"❌ 请求异常：{str(e)}")
        return None


def extract_structure_ids(first_api_response: Dict) -> List[str]:
    """从第一步接口响应中提取最内层children的id（structureId）"""
    structure_ids = []
    root_children = first_api_response.get("data", {}).get("children", [])
    for level1_child in root_children:
        level2_children = level1_child.get("children", [])
        for level2_child in level2_children:
            structure_id = level2_child.get("id")
            if structure_id:
                structure_ids.append(str(structure_id))
    return structure_ids


def filter_target_bill_total_costs(second_api_response: Dict) -> Dict[str, List[float]]:
    """
    从第二步接口响应中按类别筛选目标清单合价（前缀匹配逻辑）
    返回格式：{类别名称: [合价列表]}
    """
    # 初始化每个类别的合价列表
    category_costs = {rule[0]: [] for rule in MATCH_RULES}

    # 递归遍历所有层级的清单节点
    def traverse_children(children: List[Dict]):
        if not children:
            return
        for child in children:
            # 仅处理实际清单节点（billNode=true）
            if child.get("billNode") is True:
                # 增加bill_code非空校验
                bill_code = child.get("billCode", "")
                if bill_code is None:
                    bill_code = ""  # 确保为字符串类型，避免NoneType错误

                bill_name = child.get("name", "")
                total_cost_str = child.get("totalCost")

                if total_cost_str:
                    try:
                        total_cost = float(total_cost_str)
                        # 检查当前清单是否匹配任何类别规则（前缀匹配+名称关键词）
                        for category, code_prefixes, name_keywords in MATCH_RULES:
                            # 核心：前缀匹配（判断billCode是否以任一编码前缀开头）
                            prefix_match = any(bill_code.startswith(prefix) for prefix in code_prefixes)
                            # 名称匹配（无关键词则自动满足）
                            name_match = any(kw in bill_name for kw in name_keywords) if name_keywords else True

                            if prefix_match and name_match:
                                category_costs[category].append(total_cost)
                                # 输出匹配详情（标注匹配的前缀）
                                matched_prefixes = [p for p in code_prefixes if bill_code.startswith(p)]
                                print(
                                    f"📊 匹配[{category}]：{bill_code:25} | 匹配前缀：{','.join(matched_prefixes):20} | 名称：{bill_name:30} | 合价：{total_cost:10.2f} 元"
                                )
                    except ValueError:
                        print(f"⚠️  合价格式错误：{total_cost_str}（清单编码：{bill_code}）")

            # 递归处理子节点
            traverse_children(child.get("children", []))

    # 从data字段开始遍历
    data_list = second_api_response.get("data", [])
    for data_item in data_list:
        traverse_children(data_item.get("children", []))

    return category_costs


# -------------------------- 5. 主逻辑执行 --------------------------
if __name__ == "__main__":
    print("=" * 100)
    print("          措施项目清单合价统计工具（前缀匹配版）          ")
    print("=" * 100)

    # 步骤0：动态获取quotaId
    print("\n【步骤0：获取quotaId】")
    QUOTA_ID = get_quota_id_dynamically()
    print(f"✅ 已确认有效quotaId：{QUOTA_ID}")

    # 步骤1：获取所有structureId
    print("\n" + "=" * 100)
    print("【步骤1：获取structureId列表】")
    first_request_params = {"quotaId": QUOTA_ID}
    first_response = send_post_request(FIRST_API_URL, first_request_params)
    if not first_response:
        print("❌ 步骤1执行失败，终止程序")
        exit()
    structure_id_list = extract_structure_ids(first_response)
    print(f"\n✅ 步骤1执行完成，共提取{len(structure_id_list)}个structureId：")
    for idx, sid in enumerate(structure_id_list, 1):
        print(f"  {idx:2d}. {sid}")

    # 步骤2：批量请求清单接口，按类别筛选目标合价（前缀匹配）
    print("\n" + "=" * 100)
    print("【步骤2：批量请求清单接口，按类别前缀匹配目标合价】")
    all_category_costs = {rule[0]: [] for rule in MATCH_RULES}  # 汇总所有structureId的结果

    for sid in structure_id_list:
        print(f"\n--- 正在处理 structureId：{sid} ---")
        second_request_params = {
            "quotaId": QUOTA_ID,
            "structureId": sid,
            "queryType": 0
        }
        second_response = send_post_request(SECOND_API_URL, second_request_params)
        if not second_response:
            print(f"⚠️  跳过该structureId：{sid}（请求失败）")
            continue
        current_category_costs = filter_target_bill_total_costs(second_response)
        # 合并当前structureId的结果到总结果
        for category, costs in current_category_costs.items():
            all_category_costs[category].extend(costs)

    # 步骤3：统计各类别合价总和
    print("\n" + "=" * 100)
    print("【步骤3：各类别合价统计结果】")
    print(f"\n📋【分类统计结果】")

    # 计算总合计
    total_overall = 0.0
    for category, costs in all_category_costs.items():
        category_sum = sum(costs)
        total_overall += category_sum
        print(f"\n【{category}】")
        print(f"  匹配到的清单数量：{len(costs)} 个")
        if costs:
            print(f"  合价列表（元）：")
            for idx, cost in enumerate(costs, 1):
                print(f"    {idx:2d}. {cost:15.2f}")
        else:
            print(f"  未匹配到任何清单")
        print(f"  合价总和（元）：{category_sum:20.2f}")

    # 输出总合计
    print("\n" + "=" * 100)
    print(f"📊 所有类别总合计（元）：{total_overall:20.2f}")
    print("\n" + "=" * 100)
    print("🎉 所有步骤执行完成！")